/*
 * Copyright 2010, 2011, 2012 mapsforge.org
 *
 * This program is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.mapsforge.android.maps.overlay;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.mapsforge.android.maps.MapView;
import org.mapsforge.android.maps.Projection;
import org.mapsforge.core.model.GeoPoint;

import android.content.Intent;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;

/**
 * ArrayKMLItemizedOverlay is a thread-safe implementation of the {@link ItemizedOverlay} class using an
 * {@link ArrayList} as internal data structure. A default marker for all {@link KMLOverlayItem OverlayItems} without an
 * individual marker can be defined via the constructor.
 */
public class ArrayKMLItemizedOverlay extends ItemizedOverlay<KMLOverlayItem> {
	private static final int INITIAL_CAPACITY = 8;
	private static final String THREAD_NAME = "ArrayKMLItemizedOverlay";

	private final List<KMLOverlayItem> overlayItems;
	private String backtrackURL = null;

	/**
	 * @param defaultMarker
	 *            the default marker (may be null). This marker is aligned to the center of its bottom line to allow for
	 *            a conical symbol such as a pin or a needle.
	 */
	public ArrayKMLItemizedOverlay(Drawable defaultMarker) {
		this(defaultMarker, true);
	}

	/**
	 * @param defaultMarker
	 *            the default marker (may be null).
	 * @param alignMarker
	 *            whether the default marker should be aligned or not. If true, the marker is aligned to the center of
	 *            its bottom line to allow for a conical symbol such as a pin or a needle.
	 */
	public ArrayKMLItemizedOverlay(Drawable defaultMarker, boolean alignMarker) {
		super(defaultMarker != null && alignMarker ? ItemizedOverlay.boundCenterBottom(defaultMarker) : defaultMarker);
		this.overlayItems = new ArrayList<KMLOverlayItem>(INITIAL_CAPACITY);
	}

	/**
	 * Adds the given item to the overlay.
	 * 
	 * @param overlayItem
	 *            the item that should be added to the overlay.
	 */
	public void addItem(KMLOverlayItem overlayItem) {
		synchronized (this.overlayItems) {
			this.overlayItems.add(overlayItem);
		}
		populate();
	}

	/**
	 * This method returns an OverlayItem contained here.
	 * 
	 * @param index
	 *            is an integer
	 * @return the Item on the index position in the ArrayKMLItemizedOverlay or null if out of bounds
	 */
	public KMLOverlayItem getItem(int index) {
		// I honestly don't get why createItem is named like this, but as far as I can see
		// it's a get Item in fact...
		return createItem(index);
	}

	/**
	 * When Overriding the onTap and onTouch events is not enough, this very inefficient method returns the KMLOverlay
	 * item that has been hit in a concrete mapView by a certain geoPoint coordinates. Or null if there's nothing in
	 * such position.
	 * 
	 * @param geoPoint
	 *            the point of impact to check
	 * @param mapView
	 *            a mapView where the overlay is hold. Needed for projection etc.
	 * @return the hit OverlayItem, or null if is a miss.
	 */
	public KMLOverlayItem getItemHit(GeoPoint geoPoint, MapView mapView) {
		Projection projection = mapView.getProjection();
		Point eventPosition = projection.toPixels(geoPoint, null);

		// check if the translation to pixel coordinates has failed
		if (eventPosition == null) {
			return null;
		}

		Point checkItemPoint = new Point();

		// iterate over all items (since in this level we don't have access to visibleItems list
		for (int i = this.overlayItems.size() - 1; i >= 0; --i) {
			// get the current item
			KMLOverlayItem checkOverlayItem = this.overlayItems.get(i);
			if (checkOverlayItem == null) {
				continue;
			}

			synchronized (checkOverlayItem) {
				// make sure that the current item has a position
				if (checkOverlayItem.getPoint() == null) {
					continue;
				}

				checkItemPoint = projection.toPixels(checkOverlayItem.getPoint(), checkItemPoint);
				// check if the translation to pixel coordinates has failed
				if (checkItemPoint == null) {
					continue;
				}

				// select the correct marker for the item and get the position
				Rect checkMarkerBounds;
				if (checkOverlayItem.getMarker() == null) {
					if (this.getDefaultMarker() == null) {
						// no marker to draw the item
						continue;
					}
					checkMarkerBounds = this.getDefaultMarker().getBounds();
				} else {
					checkMarkerBounds = checkOverlayItem.getMarker().getBounds();
				}

				// calculate the bounding box of the marker
				int checkLeft = checkItemPoint.x + checkMarkerBounds.left;
				int checkRight = checkItemPoint.x + checkMarkerBounds.right;
				int checkTop = checkItemPoint.y + checkMarkerBounds.top;
				int checkBottom = checkItemPoint.y + checkMarkerBounds.bottom;

				// check if the event position is within the bounds of the marker
				if (checkRight >= eventPosition.x && checkLeft <= eventPosition.x && checkBottom >= eventPosition.y
						&& checkTop <= eventPosition.y) {
					return checkOverlayItem;
				}
			}
		}

		// no hit
		return null;
	}

	/**
	 * Adds all items of the given collection to the overlay.
	 * 
	 * @param c
	 *            collection whose items should be added to the overlay.
	 */
	public void addItems(Collection<? extends KMLOverlayItem> c) {
		synchronized (this.overlayItems) {
			this.overlayItems.addAll(c);
		}
		populate();
	}

	/**
	 * Removes all items from the overlay.
	 */
	public void clear() {
		synchronized (this.overlayItems) {
			this.overlayItems.clear();
		}
		populate();
	}

	@Override
	public String getThreadName() {
		return THREAD_NAME;
	}

	/**
	 * Removes the given item from the overlay.
	 * 
	 * @param overlayItem
	 *            the item that should be removed from the overlay.
	 */
	public void removeItem(OverlayItem overlayItem) {
		synchronized (this.overlayItems) {
			this.overlayItems.remove(overlayItem);
		}
		populate();
	}

	@Override
	public int size() {
		synchronized (this.overlayItems) {
			return this.overlayItems.size();
		}
	}

	@Override
	protected KMLOverlayItem createItem(int index) {
		synchronized (this.overlayItems) {
			if (index >= this.overlayItems.size()) {
				return null;
			}
			return this.overlayItems.get(index);
		}
	}

	/**
	 * @param backtrackURL
	 *            is a URL representing the base intent that will be called back when clocked upon
	 */
	public void setBacktrackURL(String backtrackURL) {
		this.backtrackURL = backtrackURL;
	}

	/**
	 * @return the URL representing the base intent that will be called back when clocked upon (can be null)
	 */
	public String getBacktrackURL() {
		return this.backtrackURL;
	}

	// TODO: override the onTap and such and call the backTrackURL with the id of the tapped item
	/**
	 * Checks whether an item has been tapped.
	 */
	@Override
	public boolean onTap(GeoPoint geoPoint, MapView mapView) {
		boolean handled = false;

		if (getItemHit(geoPoint, mapView) != null) {
			KMLOverlayItem kovi = getItemHit(geoPoint, mapView);
			handled = true;

			String url = this.getBacktrackURL();

			if (url != null && !url.equals("")) {
				// CALLING THE SOURCE APP
				// TODO: more error resilience!!!
				Intent intent = new Intent(this.getBacktrackURL());

				Bundle b = new Bundle();
				// TODO: use the right id ?
				b.putLong("id", 1);
				b.putLong("idSample", Long.valueOf(kovi.kmlId).longValue());
				intent.putExtras(b);

				mapView.getContext().startActivity(intent);
			}
		}
		return handled;
	}
}
